<?php

Raise::load('core.RaiseObject');
Raise::load('core.helper.config.RaiseSettings');
Raise::load('core.plugin.RaisePluginManager');
Raise::load('core.RaiseVersion');
Raise::load('core.collection.RaiseCollection');
Raise::load('core.net.http.RaiseHttpRequest');
Raise::load('core.io.file.RaiseFile');
Raise::load('core.io.file.RaisePath');
Raise::load('core.net.http.RaiseHttpStream');
Raise::load('core.archive.RaiseZip');
Raise::load('core.io.file.RaiseFileSystem');
Raise::load('core.net.RaiseUrl');
Raise::load('core.security.RaiseHashDigest');
Raise::load('core.archive.RaiseZip');

/**
 * RaisePlugin class
 * A plugin that can be loaded by RaisePluginManager
 *
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package phpraise.core.plugin
 * @abstract
 * @since 1.2
 */
abstract class RaisePlugin extends RaiseObject {

    /**
     * Settings with regards to this plugin
     * @var RaiseSettings
     */
    protected $settings;

    /**
     * The plugin manager handling this plugin
     * @var RaisePluginManager
     */
    protected $pluginManager;

    /**
     * Load the plugin
     * @param RaisePluginManager $pluginManager
     * @param RaiseSettings $settings
     */
    public function load($pluginManager, $settings){
        $this->pluginManager = $pluginManager;
        $this->settings = $settings;
        $this->initialize();
    }

    protected abstract function initialize();

    /**
     * Get the plugin manager that is handling this plugin
     * @return RaisePluginManager
     */
    public function manager(){
        return $this->pluginManager;
    }

    /**
     * Get the name of this plugin
     * @return string
     */
    public function name(){
        return $this->settings->get('plugin', 'name');
    }

    /**
     * Get the version of this plugin
     * @return RaiseVersion
     */
    public function version(){
        return new RaiseVersion($this->settings->get('plugin', 'version'));
    }

    /**
     * Get the versions of phpRaise Framework that is compatible with this plugin
     * @return RaiseCollection
     */
    public function targetFramework(){
        $fwVersion = $this->settings->get('targetFramework', 'version');
        if(!($fwVersion instanceof RaiseCollection)){
            $fwVersion = new RaiseCollection(array($fwVersion));
        }
        return $fwVersion;
    }

    /**
     * Get the update URL of this plugin
     * @return RaiseUrl
     */
    public function updateUrl(){
        $url = new RaiseUrl($this->settings->get('plugin', 'updateUrl'));
        if($url->scheme() == 'http' || $url->scheme() == 'https'){
            $url->params()->add('framework', Raise::version()->__toString());
        }else{
            $url = $this->settings->get('plugin', 'updateUrl');
        }
        return $url;
    }

    private function fetchUpdateDetails(){
            $url = $this->updateUrl();
            if($url instanceof RaiseUrl){
                $opts = array(
                  'http' => array(
                    'method' => 'GET',
                    'header' => "Accept-language: en\r\n" .
                                "Cookie: foo=bar\r\n"
                  )
                );

                $context = stream_context_create($opts);
                $updateData = file_get_contents($url, null, $context);
            }else{
                $updateData = file_get_contents($url);
            }
            return RaiseJsonConverter::decode($updateData);
    }

    /**
     * Check the update URL and determine whether an update is required
     * @return boolean
     */
    public function hasUpdate(){
        if($this->updateUrl() && $this->version()){
            $details = $this->fetchUpdateDetails();
            if($details['version'] != $this->version()->__toString()){
                return true;
            }
        }
        return false;
    }

    /**
     * Run update for this plugin
     */
    public function update(){
        if($this->updateUrl() && $this->version()){

            if(!class_exists('RaiseZip')){
                throw new RaiseInvalidRequestException('Cannot update a plugin when RaiseZip is not loaded. Note that you need to have the Zlib extension enabled.');
            }

            $details = $this->fetchUpdateDetails();
            if($details['version'] != $this->version()->__toString()){
                $url = new RaiseUrl($details['url']);

                if($url->scheme() == 'http' || $url->scheme() == 'https'){
                    $stream = new RaiseHttpStream($url->host(), ($url->port() ? $url->port(): ($url->scheme() == 'https' ? 443 : 80)));

                    $request = new RaiseHttpRequest();
                    $request->headers()->add('Host', $url->host(), true);
                    $request->uri($url->path() . ($url->params()->count() > 0 ? '?' . http_build_query($url->params()->toArray()) : ''));
                    $request->version(RaiseHttpRequest::HTTP_1_1);
                    $request->method(RaiseHttpRequest::METHOD_GET);
                    $request->headers()->add('Connection', 'Closed', true);
                    $request->headers()->add('Accept', 'text/html, application/xhtml+xml, application/xml, text/plain, */*', true);
                    $request->headers()->add('Accept-Language', 'en-us, en;q=0.5', true);
                    $request->headers()->add('Accept-Charset', 'utf-8, *', true);

                    $tmpFile = new RaiseFile(RaisePath::combine(RaisePath::tempPath(), '/pluginUpdate' . RaiseHashDigest::crc32($this->name())));
                    $tmpFile->create();
                    $fileStream = $tmpFile->stream();
                    while(true){
                        $s = $stream->read(1024);
                        $fileStream->write($s);
                        if(strlen($s) < 1024){
                            break;
                        }
                    }

                    $fileStream->close();
                    $stream->close();

                    $zip = new RaiseZip($tmpFile);
                    $zip->extract(RaisePath::combine($this->pluginManager->path(), '/' . $this->name() . ''));
                    $tmpFile->delete();
                }else{
                    $zip = new RaiseZip($details['url']);
                    $zip->extract(RaisePath::combine($this->pluginManager->path(), '/' . $this->name() . ''));
                }
            }
        }
    }

    /**
     * Uninstall the plugin
     * This action is irreversible.
     */
    public function uninstall(){
         $path = RaisePath::combine($this->pluginManager->path(), '/' . $this->name() . '');
         RaisePath::clear($path);
         RaisePath::delete($path);
    }

}